/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include <pony/sensor/ds18b20.h>
/* includes (standard library, system) ---------------------------------------*/
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <x_config.h>
#include <x_module.h>
#include <x_symbol.h>

#include <k_errno.h>

#include <hal_gpio.h>
#include <hal_delay.h>

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/
/* macro ---------------------------------------------------------------------*/

#define DS18B20_SET_DQ()  hal_gpio_write(&gpio_dq, 1)
#define DS18B20_CLR_DQ()  hal_gpio_write(&gpio_dq, 0)
#define DS18B20_READ_DQ() hal_gpio_state(&gpio_dq)

/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/

static hal_gpio_t gpio_dq;

/* variables (global) --------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

static int ds18b20_reset(void)
{
    uint8_t timeout = 0;

    DS18B20_SET_DQ();
    DELAY_US(50);

    DS18B20_CLR_DQ();
    DELAY_US(500);   // 480us ~ 960us

    DS18B20_SET_DQ();
    DELAY_US(60);   // 15us ~ 60us

    while (DS18B20_READ_DQ()) {
        timeout++;
        if (timeout > 100) {
            return -ETIMEDOUT;
        }
    }

    DELAY_MS(500);
    DS18B20_SET_DQ();

    return 0;
}

static void ds18b20_write_byte(uint8_t dat)
{
    uint8_t i;

    for (i = 0; i < 8; i++) {
        DS18B20_CLR_DQ();
        DELAY_US(5);   // 1us ~ 15us

        if (dat & 0x01) {
            DS18B20_SET_DQ();
        } else {
            DS18B20_CLR_DQ();
        }
        DELAY_US(65);   // 60us ~ 120us

        DS18B20_SET_DQ();
        DELAY_US(2);

        dat >>= 1;
    }
}

static uint8_t ds18b20_read_byte(void)
{
    uint8_t i, dat = 0;

    DS18B20_SET_DQ();
    DELAY_US(5);

    for (i = 0; i < 8; i++) {
        dat >>= 1;

        DS18B20_CLR_DQ();
        DELAY_US(5);   // 1us ~ 15us

        DS18B20_SET_DQ();
        DELAY_US(2);

        if (DS18B20_READ_DQ()) {
            dat |= 0x80;
        } else {
            dat &= 0x7F;
        }
        DELAY_US(45);
        DS18B20_SET_DQ();
        DELAY_US(2);
    }

    return dat;
}

static int ds18b20_start_convert(void)
{
    if (ds18b20_reset() != 0) {
        return -1;
    }

    ds18b20_write_byte(DS18B20_SKIP_ROM);
    ds18b20_write_byte(DS18B20_CONVERT_T);

    return 0;
}

static int ds18b20_read_scratchpad(void)
{
    if (ds18b20_reset() != 0) {
        return -1;
    }

    ds18b20_write_byte(DS18B20_SKIP_ROM);
    ds18b20_write_byte(DS18B20_READ_SCRATCHPAD);

    return 0;
}

/**
 * ds18b20_read_temp - read temperature
 * @minus: minus flag
 * @temp: temperature，expand to 100 times
 *
 * @retval 0 on success, -1 on error
 */
int ds18b20_read_temp(uint8_t *minus, uint16_t *temp)
{
    uint8_t tl, th;
    uint16_t t;

    if (ds18b20_read_scratchpad() != 0) {
        return -1;
    }

    tl = ds18b20_read_byte();   // LSB
    th = ds18b20_read_byte();   // MSB

    if (ds18b20_reset() != 0) {
        return -1;
    }

    if (th & 0xF8) {   // 0000 0111 1101 0000 - 0x07D0 -- +125C
        *minus = 1;
        t = ((th << 8) | tl);
        t = (~t) + 1;
    } else {
        *minus = 0;
        t = ((th << 8) | tl);
    }
    *temp = t * 6.25;   // 0.0625 * 100 = 6.25, expand to 100 times

    if (ds18b20_start_convert() != 0) {
        return -1;
    }

    return 0;
}

static int __ds18b20_gpio_init(void)
{
    int ret;

    gpio_dq.gpio = CONFIG_DS18B20_DQ_PORT;
    gpio_dq.pin = CONFIG_DS18B20_DQ_PIN;
    gpio_dq.mode = HAL_GPIO_MODE_OUTPUT_OD;
    gpio_dq.pull = HAL_GPIO_PULLUP;
    if ((ret = hal_gpio_init(&gpio_dq)) != 0) {
        return ret;
    }

    return 0;
}

static int ds18b20_init(void)
{
    int ret;

    if ((ret = __ds18b20_gpio_init()) != 0) {
        return ret;
    }

    if ((ret = ds18b20_reset()) != 0) {
        return ret;
    }

    ds18b20_write_byte(DS18B20_SKIP_ROM);
    ds18b20_write_byte(DS18B20_WRITE_SCRATCHPAD);
    ds18b20_write_byte(DS18B20_ALARM_TH);
    ds18b20_write_byte(DS18B20_ALARM_TL);
    ds18b20_write_byte(DS18B20_PRECISION);

    if ((ret = ds18b20_reset()) != 0) {
        return ret;
    }

    ds18b20_write_byte(DS18B20_SKIP_ROM);
    ds18b20_write_byte(DS18B20_COPY_SCRATCHPAD);

    if ((ret = ds18b20_start_convert()) != 0) {
        return ret;
    }

    return 0;
}

MODULE_INIT(driver, ds18b20_init);
